﻿using Furion.DependencyInjection;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Furion.CMS.Core.Cache
{
    /// <summary>
    /// 分布式缓存扩展
    /// </summary>
    /// <typeparam name="TCacheItem"></typeparam>
    public class DistributedCacheExtend<TCacheItem> : IDistributedCache<TCacheItem>, ISingleton
    {
        protected readonly ILogger<IDistributedCache<TCacheItem>> Logger;
        protected readonly IDistributedCache Cache;
        protected readonly SemaphoreSlim SyncSemaphore;
        protected DistributedCacheEntryOptions DefaultCacheOptions;

        /// <summary>
        /// 分布式缓存构造函数
        /// </summary>
        /// <param name="distributedCache"></param>
        /// <param name="logger"></param>
        public DistributedCacheExtend(IDistributedCache distributedCache,
            ILogger<IDistributedCache<TCacheItem>> logger)
        {
            this.Cache = distributedCache;
            this.Logger = logger;
            SyncSemaphore = new SemaphoreSlim(1, 1);

            SetDefaultOptions();
        }

        /// <summary>
        /// 设置默认选项
        /// </summary>
        protected virtual void SetDefaultOptions()
        {
            //Configure default cache entry options
            DefaultCacheOptions = GetDefaultCacheEntryOptions();
        }

        /// <summary>
        /// 默认缓存配置
        /// <para>
        /// 绝对过期时间为8小时
        /// 缓存2小时内未访问将会被移除
        /// </para>
        /// </summary>
        /// <returns></returns>
        protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions()
        {
            return new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(8),
                SlidingExpiration = TimeSpan.FromHours(2),
            };
        }

        public TCacheItem Get(string key, bool hideErrors = true)
        {
            byte[] cachedBytes = default;
            try
            {
                cachedBytes = this.Cache.Get(key);
            }
            catch (Exception ex)
            {
                var errorMessage = $"获取键为 {key} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
            return ToCacheItem(cachedBytes);
        }

        public async Task<TCacheItem> GetAsync(string key, bool hideErrors = true, CancellationToken token = default)
        {
            byte[] cachedBytes = default;
            try
            {
                cachedBytes = await this.Cache.GetAsync(key);
            }
            catch (Exception ex)
            {
                var errorMessage = $"获取键为 {key} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
            return ToCacheItem(cachedBytes);
        }

        public async Task<KeyValuePair<string, TCacheItem>[]> GetManyAsync(IEnumerable<string> keys, bool hideErrors = true, CancellationToken token = default)
        {
            var keyArray = keys.ToArray();
            var cachedValues = new List<KeyValuePair<string, TCacheItem>>();

            try
            {
                foreach (string key in keyArray)
                {
                    var value = await GetAsync(key);
                    cachedValues.Add(KeyValuePair.Create<string, TCacheItem>(key, value));
                }
            }
            catch (Exception ex)
            {
                var errorMessage = $"批量获取键为 {string.Join(",", keys)} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
            return cachedValues.ToArray();
        }

        public TCacheItem GetOrAdd(string key, Func<TCacheItem> factory, DistributedCacheEntryOptions factoryOptions = default, bool hideErrors = true)
        {
            var value = Get(key);
            if (value != null) return value;

            try
            {
                SyncSemaphore.Wait();
                // 添加前再次尝试获取一次
                value = Get(key);
                if (value != null)
                {
                    return value;
                }

                value = factory();

                Set(key, value, factoryOptions ?? DefaultCacheOptions);
            }
            catch (Exception ex)
            {
                var errorMessage = $"键为 {key} 的数据不存在时,尝试添加出现错误";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
            finally
            {
                SyncSemaphore.Release();
            }
            return value;
        }

        public async Task<TCacheItem> GetOrAddAsync(string key, Func<Task<TCacheItem>> factory, DistributedCacheEntryOptions factoryOptions = default, bool hideErrors = true, CancellationToken token = default)
        {
            var value = await GetAsync(key, hideErrors, token);
            if (value != null) return value;

            try
            {
                await SyncSemaphore.WaitAsync(token);

                value = await GetAsync(key, hideErrors, token);
                if (value != null)
                {
                    return value;
                }

                value = await factory();

                await SetAsync(key, value, factoryOptions ?? DefaultCacheOptions, hideErrors, token);
            }
            catch (Exception ex)
            {
                var errorMessage = $"键为 {key} 的数据不存在时,尝试添加出现错误";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
            finally
            {
                SyncSemaphore.Release();
            }

            return value;
        }

        public async Task<KeyValuePair<string, TCacheItem>[]> GetOrAddManyAsync(IEnumerable<string> keys, Func<IEnumerable<string>, Task<List<KeyValuePair<string, TCacheItem>>>> factory, DistributedCacheEntryOptions factoryOptions = null, bool hideErrors = true, CancellationToken token = default)
        {
            KeyValuePair<string, TCacheItem>[] result = default;
            var keyArray = keys.ToArray();

            result = await GetManyAsync(keys, hideErrors, token);

            if (result.All(x => x.Value != null))
            {
                return result;
            }

            var missingKeys = new List<string>();
            var missingValuesIndex = new List<int>();
            for (var i = 0; i < keyArray.Length; i++)
            {
                if (result[i].Value != null)
                {
                    continue;
                }

                missingKeys.Add(keyArray[i]);
                missingValuesIndex.Add(i);
            }

            try
            {
                var missingValues = (await factory.Invoke(missingKeys)).ToArray();
                var valueQueue = new Queue<KeyValuePair<string, TCacheItem>>(missingValues);

                if (missingValues.Any())
                {
                    await SetManyAsync(missingValues, factoryOptions, hideErrors, token);

                    foreach (var index in missingValuesIndex)
                    {
                        result[index] = valueQueue.Dequeue();
                    }
                }
            }
            catch (Exception ex)
            {
                var errorMessage = $"键为 {string.Join(",", missingKeys)} 的数据不存在时,尝试添加出现错误";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }

            return result;
        }


        public void Refresh(string key, bool hideErrors = true)
        {
            try
            {
                this.Cache.Refresh(key);
            }
            catch (Exception ex)
            {
                var errorMessage = $"刷新键为 {key} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
        }

        public async Task RefreshAsync(string key, bool hideErrors = true, CancellationToken token = default)
        {
            try
            {
                await this.Cache.RefreshAsync(key, token);
            }
            catch (Exception ex)
            {
                var errorMessage = $"刷新键为 {key} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
        }

        public void Remove(string key, bool hideErrors = true)
        {
            try
            {
                this.Cache.Remove(key);
            }
            catch (Exception ex)
            {
                var errorMessage = $"移除键为 {key} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
        }

        public async Task RemoveAsync(string key, bool hideErrors = true, CancellationToken token = default)
        {
            try
            {
                await this.Cache.RemoveAsync(key, token);
            }
            catch (Exception ex)
            {
                var errorMessage = $"移除键为 {key} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
        }

        public void Set(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool hideErrors = true)
        {
            try
            {
                this.Cache.Set(key, JsonSerializer.SerializeToUtf8Bytes(value), options ?? DefaultCacheOptions);
            }
            catch (Exception ex)
            {
                var errorMessage = $"设置键为 {key} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
        }

        public async Task SetAsync(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool hideErrors = true, CancellationToken token = default)
        {
            try
            {
                await this.Cache.SetAsync(key, JsonSerializer.SerializeToUtf8Bytes(value), options ?? DefaultCacheOptions, token);
            }
            catch (Exception ex)
            {
                var errorMessage = $"设置键为 {key} 的数据出错";
                Logger.LogError(ex, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, ex);
            }
        }

        public Task SetManyAsync(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions options = default, bool hideErrors = true, CancellationToken token = default)
        {
            var errorQueue = new ConcurrentQueue<Exception>();
            // 并行设置
            Parallel.ForEach(items, new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = 5 }, item =>
            {
                try
                {
                    Set(item.Key, item.Value, options ?? DefaultCacheOptions, hideErrors);
                }
                catch (Exception ex)
                {
                    errorQueue.Enqueue(ex);
                }
            });

            if (errorQueue.Any())
            {
                var errorMessage = "批量设置缓存出错";
                var aggregateException = new AggregateException(errorQueue);
                Logger.LogError(aggregateException, errorMessage);
                if (!hideErrors) throw new Exception(errorMessage, aggregateException);
            }

            return Task.CompletedTask;
        }

        /// <summary>
        /// 转换
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        protected virtual TCacheItem ToCacheItem(byte[] bytes)
        {
            if (bytes == null)
            {
                return default;
            }

            return JsonSerializer.Deserialize<TCacheItem>(bytes);
        }
    }
}
